package com.lwk.springframework;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.beans.Introspector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LwkApplicationContext {

    private List<String> classNames = new ArrayList<>();

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    // 一级缓存 单例池
    private Map<String, Object> singletonObjects = new HashMap<>();

    public LwkApplicationContext(Class configClass) {
        // 解析配置类，扫描对象放入beanDefinitionMap
        scan(configClass);

        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            // 非懒加载的单例Bean
            if (!beanDefinition.isLazy() && "singleton".equals(beanDefinition.getScope())) {
                // 创建bean
                if (!singletonObjects.containsKey(beanName)) {
                    Object bean = creatBean(beanName, beanDefinition);
                    singletonObjects.put(beanName,bean);
                }

            }
        }

    }

    public Object creatBean(String beanName,BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            instance = clazz.newInstance();

            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    field.set(instance, getBean(field.getType()));
                }
            }

            // Aware接口
            if (instance instanceof ApplicationContextAware) {
                ((ApplicationContextAware) instance).setApplicationContext(this);
            }

            // AOP模拟事务
            if (clazz.isAnnotationPresent(Transactional.class)) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(clazz);
                Object target = instance;
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        System.out.println("开启事务");
                        Object result = method.invoke(target, objects);
                        System.out.println("提交事务");
                        return result;
                    }
                });
                instance = enhancer.create();
            }

        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return instance;
    }

    public Object getBean(Class type) {
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (beanDefinition.getType().equals(type)) {
                return getBean(beanName);
            }
        }

        return null;

    }

    public Object getBean(String beanName) {
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new RuntimeException("BeanName is UnDefinition:"+beanName);
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if ("singleton".equals(beanDefinition.getScope())) {
            Object bean = singletonObjects.get(beanName);
            // 懒加载可能为null
            if (bean == null) {
                bean = creatBean(beanName, beanDefinition);
                singletonObjects.put(beanName,bean);
            }
            return bean;
        } else {
            return creatBean(beanName, beanDefinition);
        }
    }

    private void scan(Class configClass) {
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan annotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = annotation.value();
            ClassLoader classLoader = this.getClass().getClassLoader();
            URL url = classLoader.getResource(path.replaceAll("\\.", "/"));
            File file = new File(url.getFile());
            getClassNames(file);

            for (String className : classNames) {
                try {
                    Class<?> clazz = classLoader.loadClass(className);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setType(clazz);
                        beanDefinition.setLazy(clazz.isAnnotationPresent(Lazy.class));
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
                        } else {
                            beanDefinition.setScope("singleton");
                        }

                        String beanName = clazz.getAnnotation(Component.class).value();
                        if (beanName.isEmpty()) {
                            beanName = Introspector.decapitalize(clazz.getSimpleName());
                        }
                        beanDefinitionMap.put(beanName,beanDefinition);
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    private void getClassNames(File file) {
        if (file.isDirectory()) {
            for (File cFile : file.listFiles()) {
                if (cFile.isDirectory()) {
                    getClassNames(cFile);
                } else {
                    String absolutePath = cFile.getAbsolutePath();
                    String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"))
                            .replace("\\",".");
                    classNames.add(className);
                }
            }
        }
    }
}
